/*
 * Copyright (c) 2010, Katholieke Universiteit Leuven
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *     * Neither the name of the Katholieke Universiteit Leuven nor the names of
 *       its contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

#include "../api/introspection.h"
#include "net.h"

static uint8_t ibuffer[LOOCI_COMMANDS_BUFSIZE + sizeof(struct command_request)];
static struct command_request * request = (struct command_request *) ibuffer;
static uint8_t obuffer[LOOCI_COMMANDS_BUFSIZE + sizeof(struct command_reply)];
static struct command_reply * reply = (struct command_reply *) obuffer;

bool do_buffer_command(size_t req_len, char * nodeID, uint8_t * buffer, size_t * size) {
  size_t oplen = LOOCI_COMMANDS_BUFSIZE;
  bool success = send_command(nodeID, request, req_len, reply, &oplen);
  if(success == false || oplen < 2) { // unrecoverable error
    *size = 0;
    return false;
  }
  uint8_t rlen = reply->payload[1];
  int i = 0;
  for(i = 0; i < rlen && i < *size && i < oplen-2; ++i) {
    buffer[i] = reply->payload[2+i];
  }
  if(i == rlen) {
    *size = rlen;
    return (bool) reply->payload[0];
  } else { // we didn't get to the end of the reply payload ...
    * size = i;
    return false;
  }
}

bool looci_get_component_ids_by_type(char * componentType, char * nodeID, uint8_t * buffer, size_t * size) {
  request->cmd = COMMAND_GET_COMPONENTS_TYPE;
  strncpy(request->payload, componentType, LOOCI_COMMANDS_BUFSIZE);
  size_t req_len = strlen(componentType) + 1; // +1 -> the terminating null character
  if(req_len > LOOCI_COMMANDS_BUFSIZE) {
    // input string was too large, make sure the payload is '\0' terminated
    request->payload[LOOCI_COMMANDS_BUFSIZE-1] = 0;
    req_len = LOOCI_COMMANDS_BUFSIZE;
  }
  return do_buffer_command(req_len, nodeID, buffer, size);
}

bool looci_get_component_ids(char * nodeID, uint8_t * buffer, size_t * size) {
  request->cmd = COMMAND_GET_COMPONENTS_ALL;
  return do_buffer_command(0, nodeID, buffer, size);
}

bool looci_get_componenttype(uint8_t componentID, char * nodeID, char * type, size_t maxlen) {
  request->cmd = COMMAND_GET_COMPONENTTYPE;
  request->payload[0] = componentID;
  size_t oplen = LOOCI_COMMANDS_BUFSIZE;
  bool success = send_command(nodeID, request, 1, reply, &oplen);
  if(success == false || oplen < 1) {
    return false;
  } else {
    strncpy(type, reply->payload + 1, maxlen);
    size_t len = strlen(reply->payload + 1) + 1;
    if(len > maxlen) {
      type[maxlen-1] = 0;
      return false;
    } else {
      return (bool) reply->payload[0];
    }
  }
}

uint8_t looci_get_state(uint8_t componentID, char * nodeID) {
  request->cmd = COMMAND_GET_STATE;
  request->payload[0] = componentID;
  size_t oplen = 1;
  bool success = send_command(nodeID, request, 1, reply, &oplen);
  if(success==false || oplen!=1) {
    fprintf(stderr, "Something wrong while sending get state command or reading the reply\r\n");
    return COMPONENT_STATE_NONE;
  }
  return reply->payload[0];
}

static bool get_int_rec(uint8_t cmd, uint8_t componentID, char * nodeID, uint8_t * buffer, size_t * size) {
  request->cmd = cmd;
  request->payload[0] = componentID;
  return do_buffer_command(1, nodeID, buffer, size);
}

bool looci_get_interfaces(uint8_t componentID, char * nodeID, uint8_t * buffer, size_t * size) {
  return get_int_rec(COMMAND_GET_INTERFACES, componentID, nodeID, buffer, size);
}

bool looci_get_receptacles(uint8_t componentID, char * nodeID, uint8_t * buffer, size_t * size) {
  return get_int_rec(COMMAND_GET_RECEPTACLES, componentID, nodeID, buffer, size);
}

bool looci_get_outgoing_wires(uint8_t interfaceEvent, uint8_t componentID, char * nodeID, struct looci_outgoing_wire * buffer, size_t * size) {
  request->cmd = COMMAND_GET_OUTGOING_WIRES;
  request->payload[0] = interfaceEvent;
  request->payload[1] = componentID;
  size_t oplen = LOOCI_COMMANDS_BUFSIZE;
  bool success = send_command(nodeID, request, 2, reply, &oplen);
  if(success == false || oplen < 2) { // unrecoverable error
    *size = 0;
    return false;
  }
  uint8_t rlen = reply->payload[1];
  int i = 0;
  for(i = 0; i < rlen && i < *size && i < (oplen-2)/16; ++i) { // 16 = size of ip6_addr_t
    inet_ntop(AF_INET6, reply->payload + 2 + (i * 16), buffer[i].nodeID, WIRE_NODEID_LENGTH);
  }
  *size = i;
  if(i == rlen) {
    return (bool) reply->payload[0];
  } else { // we didn't get to the end of the reply payload ...
    return false;
  }

}

bool looci_get_incoming_wires(uint8_t interfaceEvent, uint8_t componentID, char * nodeID, struct looci_incoming_wire * buffer, size_t * size) {
  request->cmd = COMMAND_GET_INCOMING_WIRES;
  request->payload[0] = interfaceEvent;
  request->payload[1] = componentID;
  size_t oplen = LOOCI_COMMANDS_BUFSIZE;
  bool success = send_command(nodeID, request, 2, reply, &oplen);
  if(success == false || oplen < 2) { //unrecoverable error
    *size = 0;
    return false;
  }
  uint8_t rlen = reply->payload[1];
  int i = 0;
  for(i=0; i < rlen && i < *size && i < (oplen-2)/17; ++i) { // 17 = size of ipv6 address + component id
    inet_ntop(AF_INET6, reply->payload + 2 + (i * 17), buffer[i].nodeID, WIRE_NODEID_LENGTH);
    buffer[i].componentID = *(reply->payload + 2 + i * 17 + 16);
  }
  *size = i;
  if(i==rlen) {
    return (bool) reply->payload[0];
  } else {
    return false;
  }
}

bool looci_get_local_wires(uint8_t interfaceEvent, uint8_t componentID, char * nodeID, uint8_t * buffer, size_t * size) {
  request->cmd = COMMAND_GET_LOCAL_WIRES;
  request->payload[0] = interfaceEvent;
  request->payload[1] = componentID;
  return do_buffer_command(2, nodeID, buffer, size);
}
